#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <cerrno>
#include <cstring>

#include <boost/thread.hpp>
#include <boost/lexical_cast.hpp>

#include "common.h"

#include "main_thread.h"
#include "config_thread.h"

using namespace boost::asio;

MAIN_THREAD::MAIN_THREAD(ADMINS * a, TARIFFS * t, USERS * u, const SETTINGS * s)
    : io_service(),
      acceptor(io_service),
      timer(io_service),
      running(true),
      port("44000"),
      maxConnections(60),
      admins(a),
      tariffs(t),
      users(u),
      settings(s)
{
}

MAIN_THREAD::~MAIN_THREAD()
{
}

void MAIN_THREAD::operator() ()
{
    if (!InitNetwork()) {
        return;
    }

    connection.reset(new CONFIG_THREAD(admins, tariffs, users, settings));
    acceptor.async_accept(
            connection->GetSocket(),
            boost::bind(&MAIN_THREAD::AcceptConnection, this, connection,
            boost::asio::placeholders::error));
    timer.expires_from_now(boost::posix_time::seconds(5));
    timer.async_wait(boost::bind(&MAIN_THREAD::AcceptTimeout, this));

    io_service.run();
    /*while (running) {
        if (WaitConnection()) {
            AcceptConnection();
        }
        if (counter == 0) {
            CleanupThreads();
        }
        ++counter;
        counter = counter % 10; // Every 5 sec
    }*/
}

bool MAIN_THREAD::InitNetwork()
{
    // Create
    ip::tcp::endpoint endpoint(ip::tcp::v4(), boost::lexical_cast<int>(port));
    acceptor.open(endpoint.protocol());
    acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
    // Bind
    acceptor.bind(endpoint);
    // Listen
    acceptor.listen();

    return true;
}

void MAIN_THREAD::AcceptConnection(boost::shared_ptr<CONFIG_THREAD> connection,
                                   const boost::system::error_code & error)
{
    timer.cancel();
    if (error) {
        // Log error
        printfd(__FILE__, "MAIN_THREAD::AcceptConnection() %s\n", boost::system::system_error(error).what());
    }
    if (!running) {
        // Stop
        return;
    }

    if (connections.size() >= maxConnections) {
        CleanupThreads();
        if (connections.size() >= maxConnections) {
            // Do not create new connection object, use current
            acceptor.async_accept(
                    connection->GetSocket(),
                    boost::bind(&MAIN_THREAD::AcceptConnection, this, connection,
                    boost::asio::placeholders::error));
            return;
        }
    }

    if (!error) {
        // Accept connection
        connections.push_back(connection);
        boost::thread thread(boost::ref(*connection));
        thread.detach();

        // Prepare for new connection
        connection.reset(new CONFIG_THREAD(admins, tariffs, users, settings));
    }

    acceptor.async_accept(
            connection->GetSocket(),
            boost::bind(&MAIN_THREAD::AcceptConnection, this, connection,
            boost::asio::placeholders::error));
}

void MAIN_THREAD::AcceptTimeout()
{
    // Check stopping
    if (!running) {
        acceptor.close();
        return;
    }

    // Remove done threads
    CleanupThreads();

    // Shedule new timeout
    timer.expires_from_now(boost::posix_time::seconds(5));
    timer.async_wait(boost::bind(&MAIN_THREAD::AcceptTimeout, this));
}

void MAIN_THREAD::CleanupThreads()
{
    connections.remove_if(
            std::mem_fun(&CONFIG_THREAD::IsDone));
    printfd(__FILE__, "MAIN_THREAD::CleanupThreads() Active threads: %d\n", connections.size());
}
